/*
 * Copyright 2012 Gregor Schauer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package at.schauer.gregor.vienna.serializer.builder;

import at.schauer.gregor.vienna.context.ViennaContext;
import at.schauer.gregor.vienna.serializer.builder.discover.ParameterDiscoverer;
import at.schauer.gregor.vienna.util.ViennaUtils;
import org.apache.commons.lang3.ClassUtils;
import org.springframework.beans.BeanUtils;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Gregor Schauer
 */
public class ParameterDiscovererBuilder<T> implements ObjectBuilder<T> {
	protected Map<Class<?>, ParameterDiscoverer> discovererMap;
	protected ViennaContext ctx;

	public ParameterDiscovererBuilder(ViennaContext ctx) {
		this(ctx, new HashMap<Class<?>, ParameterDiscoverer>());
	}

	public ParameterDiscovererBuilder(ViennaContext ctx, Map<Class<?>, ParameterDiscoverer> discovererMap) {
		this.ctx = ctx;
		this.discovererMap = discovererMap;
	}

	@Override
	public <E extends T> E construct(Map<String, Object> map, Class<E> clazz) {
		ParameterDiscoverer discoverer = discovererMap.get(clazz);
		if (discoverer == null) {
			List<Class<?>> classes = ClassUtils.getAllSuperclasses(clazz);
			classes.addAll(ClassUtils.getAllInterfaces(clazz));

			for (Class<?> type : classes) {
				discoverer = discovererMap.get(type);
				if (discoverer != null) {
					discovererMap.put(clazz, discoverer);
					break;
				}
			}

			if (discoverer == null) {
				throw new RuntimeException();
			}
		}

		try {
			Constructor<E> ctor = clazz.getDeclaredConstructor(discoverer.getParameterTypes());
			String[] parameterNames = discoverer.getParameterNameDiscoverer().getParameterNames(ctor);

			Class<?>[] parameterTypes = discoverer.getParameterTypes();
			Object[] args = new Object[discoverer.getParameterTypes().length];

			for (int i = 0; i < parameterTypes.length; i++) {
				args[i] = map.get(parameterNames[i]);
				if (args[i] instanceof String) {
					args[i] = ViennaUtils.DeserializationUtils.load(ctx, parameterTypes[i], (String) args[i]);
				}
			}
			return BeanUtils.instantiateClass(ctor, args);
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}

	public Map<Class<?>, ParameterDiscoverer> getDiscovererMap() {
		return discovererMap;
	}

	public void setDiscovererMap(Map<Class<?>, ParameterDiscoverer> discovererMap) {
		this.discovererMap = discovererMap;
	}
}
